﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Reflection;

namespace WorkNodeFramework
{
    public class WorkNodeReader<T>:IEnumerable<T>
        where T:WorkNode
    {
        NodeEnumerator<T>[] nodeEnumerators;        
        Predicate<T>[] predicates;
        int startIndex = 0;
        int stopIndex = int.MaxValue - 1;

        internal WorkNodeReader(NodeEnumerator<T>[] nodeEnumerators)
            :this(nodeEnumerators,null)
        { }

        internal WorkNodeReader(NodeEnumerator<T>[] nodeEnumerators,int pageIndex, int pageCount)
            : this(nodeEnumerators, null,pageIndex,pageCount)
        { 
        }

        internal WorkNodeReader(NodeEnumerator<T>[] nodeEnumerators, Predicate<T>[] predicates)
        {
            this.nodeEnumerators = nodeEnumerators;
            if(predicates!=null && predicates.Length>0)
                this.predicates = predicates;
        }

        internal WorkNodeReader(NodeEnumerator<T>[] nodeEnumerators,Predicate<T>[] predicates, 
            int pageIndex, int pageCount)
            :this(nodeEnumerators,predicates)
        {
            this.startIndex = pageIndex*pageCount;
            this.stopIndex = (pageIndex +1) * pageCount-1;
        }

        WorkNodeEnumerator<T> enumerator;
        WorkNodeEnumerator<T> GetWorkNodeEnumerator()
        {
            if (enumerator == null)
            {
                enumerator = new WorkNodeEnumerator<T>(nodeEnumerators, predicates, null, startIndex, stopIndex);
            }
            return enumerator;
        }

        #region IEnumerable<WorkNode> Members
        
        public IEnumerator<T> GetEnumerator()
        {
            WorkNodeEnumerator<T> en = GetWorkNodeEnumerator();
            if (en.IsClosed)
                return en.Nodes.GetEnumerator();
            return en;
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetWorkNodeEnumerator();
        }

        #endregion

        public void Sort(params Comparison<T>[] orders)
        {
            if (orders.Length == 0)
                return;
            if(enumerator !=null)
                enumerator.Reset();
            else
                enumerator = GetWorkNodeEnumerator();
            if (orders.Length > 0)
                enumerator.Comparisons = orders;
            enumerator.Predicates = predicates;
            while (enumerator.MoveNext()) ;
        }

        public List<T> ToList()
        {
            WorkNodeEnumerator<T> en = GetWorkNodeEnumerator();
            while (en.MoveNext()) ;
            return en.Nodes;
        }
    }

    public class WorkNodeEnumerator<T> : IEnumerator<T>
        where T:WorkNode
    {
        NodeEnumerator<T>[] nodeEnumerators;
        Comparison<T>[] comparisons;
        Predicate<T>[] predicates;
        int startIndex = 0;
        int stopIndex = int.MaxValue-1;
        int count = -1;

        internal WorkNodeEnumerator(NodeEnumerator<T>[] nodeEnumerators)
            :this(nodeEnumerators,null,null)
        {
        }

        internal WorkNodeEnumerator(NodeEnumerator<T>[] nodeEnumerators, int startIndex, int stopIndex)
            : this(nodeEnumerators, null, null, startIndex, stopIndex)
        {
        }

        internal WorkNodeEnumerator(NodeEnumerator<T>[] nodeEnumerators, Predicate<T>[] predicates, Comparison<T>[] comparisons)
        {
            this.nodeEnumerators = nodeEnumerators;
            this.predicates = predicates;
            this.comparisons = comparisons;
        }

        internal WorkNodeEnumerator(NodeEnumerator<T>[] nodeEnumerators, Predicate<T>[] predicates, Comparison<T>[] comparisons, int startIndex, int stopIndex)
            :this(nodeEnumerators,predicates,comparisons)
        {
            this.startIndex = startIndex;
            this.stopIndex = stopIndex;
        }

        List<T> nodes = new List<T>();
        internal List<T> Nodes
        {
            get
            {
                return nodes;
            }
        }

        bool readerClosed = false;
        void Close()
        {
            if (!readerClosed)
            {
                foreach(NodeEnumerator<T> ne in nodeEnumerators)
                    ne.Close();
                readerClosed = true;
                //=====================
                if (startIndex > 0)
                {
                    int lastIndex = nodes.Count-1;
                    if (lastIndex < startIndex)
                        nodes.Clear();
                    else
                    {
                        for (int i = 0; i < startIndex; i++)
                            nodes.RemoveAt(0);
                    }
                }
            }
        }

        internal Predicate<T>[] Predicates
        {
            get
            {
                return predicates;
            }
            set
            {
                predicates = value;
            }
        }

        internal Comparison<T>[] Comparisons
        {
            get
            {
                return comparisons;
            }
            set
            {
                comparisons = value;
            }
        }

        T current;
        #region IEnumerator<T> Members

        public T Current
        {
            get {
                return current;
            }
        }

        public bool IsClosed {
            get {
                foreach(NodeEnumerator<T> ne in nodeEnumerators)
                    if(!ne.IsClosed)
                        return false;
                return true;
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Close();
        }

        #endregion

        #region IEnumerator Members

        object System.Collections.IEnumerator.Current
        {
            get 
            {
                return (object)Current;
            }
        }

        int nodeEnumeratorCurrentIndex = 0;
        public bool MoveNext()
        {
            if (IsClosed)
                return false;
            if (count >= stopIndex && comparisons == null)
                goto FINAL;
            
            int index = nodeEnumeratorCurrentIndex;
            for(int i=index;i<nodeEnumerators.Length;i++){
                nodeEnumeratorCurrentIndex = i;
                NodeEnumerator<T> ne = nodeEnumerators[i];
                while (ne.MoveNext())
                {
                    T obj = ne.Current;
                    if (!DoPredicate(obj))
                        continue;
                    count++;
                    //=====================
                    Insert(obj);
                    current = obj;
                    return true;
                }
            }
            
            FINAL:
                current = null;
                Close();
            return false;
        }

        public void Reset()
        {
            foreach(NodeEnumerator<T> n in nodeEnumerators)
                n.Reset();
            nodeEnumeratorCurrentIndex = 0;
            current = null;
            count = -1;
        }

        #endregion

        bool DoPredicate(T obj)
        {
            if (predicates == null)
                return true;
            foreach (Predicate<T> p in predicates)
                if (!p(obj))
                    return false;
            return true;
        }

        void Insert(T node)
        {
            if (comparisons == null)
            {
                nodes.Add(node);
                return;
            }

            int lastIndex = nodes.Count-1;
            int pos = 0;
            for (int i = lastIndex; i >= 0; i--)
            {
                //if node > nodes[i]then break
                if (DoComparison(node, nodes[i]))
                {
                    pos = i + 1;
                    break;
                }

                pos = i;
            }

            if (pos == lastIndex + 1)
            {
                if (lastIndex < stopIndex)
                    nodes.Add(node);
            }
            else
            {
                if(lastIndex >= stopIndex)
                    nodes.RemoveAt(lastIndex);
                if(pos == lastIndex)
                    nodes.Add(node);
                else
                    nodes.Insert(pos,node);
            }
        }

        bool DoComparison(T node1, T node2)
        {
            foreach (Comparison<T> comp in comparisons)
                if (comp(node1,node2) < 0)
                    return false;
            return true;
        }
    }
}
